<script lang="ts">
  export default {
    name: 'user',
  };
</script>

<template>
  <div class="app-container">
    <el-row :gutter="20">
      <!-- 部门树 -->
      <el-col :span="4" :xs="24">
        <el-card class="box-card">
          <el-input
            v-model="deptName"
            placeholder="部门名称"
            clearable
            :prefix-icon="Search"
            style="margin-bottom: 20px"
          />
          <el-tree
            ref="deptTreeRef"
            :data="deptOptions"
            :props="{ children: 'children', label: 'label', disabled: '' }"
            :expand-on-click-node="false"
            :filter-node-method="filterDeptNode"
            accordion
            @node-click="handleDeptNodeClick"
          ></el-tree>
        </el-card>
      </el-col>

      <!-- 用户数据 -->
      <el-col :span="20" :xs="24">
        <el-card class="box-card">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
            <el-row>
              <el-col :span="18" :xs="24">
                <el-form-item>
                  <el-button
                    type="success"
                    :icon="Plus"
                    @click="handleAdd"
                    v-hasPerm="[POST + AUTH_PATH_WEB_PREFIX + '/account/registered/password/let']"
                  >新增
                  </el-button>
                  <el-button
                    type="danger"
                    :icon="Delete"
                    :disabled="multiple"
                    @click="handleDelete"
                    v-hasPerm="[DELETE + AUTH_PATH_WEB_PREFIX + '/user/list']"
                  >删除
                  </el-button>
                  <el-button
                    type="danger"
                    :icon="SwitchButton"
                    :disabled="multiple"
                    @click="handleKickout"
                    v-hasPerm="[DELETE + AUTH_PATH_WEB_PREFIX + '/account/kickout']"
                  >踢下线
                  </el-button>
                </el-form-item>
                <el-form-item prop="search">
                  <el-input v-model="queryParams.search" placeholder="用户名/昵称/手机号" clearable style="width: 200px" @keyup.enter="handleQuery"/>
                </el-form-item>
                <el-form-item prop="state">
                  <el-select v-model="queryParams.state" placeholder="状态" clearable style="width: 200px">
                    <el-option label="正常" value="enabled"/>
                    <el-option label="禁用" value="stop"/>
                  </el-select>
                </el-form-item>

                <el-form-item>
                  <el-button
                    type="primary"
                    :icon="Search"
                    @click="handleQuery"
                    v-hasPerm="[GET + AUTH_PATH_WEB_PREFIX + '/user/page']"
                  >搜索
                  </el-button>
                  <el-button
                    :icon="Refresh"
                    @click="resetQuery"
                    v-hasPerm="[GET + AUTH_PATH_WEB_PREFIX + '/user/page']"
                  >重置
                  </el-button>
                </el-form-item>
              </el-col>

              <el-col :span="6" :xs="24" style="text-align: right">
                <el-form-item>
                  <el-dropdown split-button style="margin-left: 12px">
                    导入
                    <template #dropdown>
                      <el-dropdown-menu>
                        <el-dropdown-item :icon="Download" @click="handleDownloadTemplate">下载模板</el-dropdown-item>
                        <el-dropdown-item :icon="Top" @click="showImportDialog">导入数据</el-dropdown-item>
                      </el-dropdown-menu>
                    </template>
                  </el-dropdown>
                  <el-button :icon="Download" style="margin-left: 12px" @click="handleExport">导出</el-button>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>

          <el-table
            v-loading="loading"
            :data="userList"
            @selection-change="handleSelectionChange"
            border
            style="width: 100%"
            v-hasPerm="[GET + AUTH_PATH_WEB_PREFIX + '/user/page']"
          >
            <el-table-column type="selection" align="center" min-width="5%"/>
            <el-table-column label="姓名" prop="realName" align="center" min-width="15%"/>
            <el-table-column label="昵称" prop="nickname" align="center" min-width="15%"/>
            <el-table-column label="性别" prop="genderMessage" align="center" min-width="10%"/>
            <el-table-column label="部门" prop="deptName" align="center" min-width="10%"/>
            <el-table-column label="手机" prop="phone" align="center" min-width="16%"/>
            <el-table-column label="邮箱" prop="email" align="center" min-width="18%"/>
            <el-table-column label="状态" prop="state" align="center" min-width="10%">
              <template #default="scope">
                <el-switch
                  v-model="scope.row.state"
                  :inactive-value="'stop'"
                  :active-value="'enabled'"
                  @change="handleStatusChange(scope.row)"
                />
              </template>
            </el-table-column>
            <el-table-column label="注册时间" prop="createDate" align="center" min-width="15%"/>
            <el-table-column label="在线状态" align="center" min-width="13%">
              <template #default="scope">
                <el-tag v-if="scope.row.onlineState" type="success">在线</el-tag>
                <el-tag v-else type="info">离线</el-tag>
              </template>
            </el-table-column>

            <el-table-column
              label="操作"
              align="center"
              min-width="20%"
              v-hasPerm="[
                PATCH + AUTH_PATH_WEB_PREFIX + '/user/{id}'
                , DELETE + AUTH_PATH_WEB_PREFIX + '/user/list'
                , PATCH + AUTH_PATH_WEB_PREFIX + '/account/reset/password'
                , DELETE + AUTH_PATH_WEB_PREFIX + '/account/kickout'
              ]"
            >
              <template #default="scope">
                <div>
                <el-button
                  type="primary"
                  :icon="Edit"
                  circle
                  plain
                  @click="handleUpdate(scope.row)"
                  v-hasPerm="[PATCH + AUTH_PATH_WEB_PREFIX + '/user/{id}']"
                ></el-button>
                <el-button
                  type="danger"
                  :icon="Delete"
                  circle
                  plain
                  @click="handleDelete(scope.row)"
                  v-hasPerm="[DELETE + AUTH_PATH_WEB_PREFIX + '/user/list']"
                ></el-button>
                </div>

                <div>
                <el-button
                  type="warning"
                  :icon="Lock"
                  circle
                  plain
                  @click="resetPassword(scope.row)"
                  v-hasPerm="[PATCH + AUTH_PATH_WEB_PREFIX + '/account/reset/password']"
                ></el-button>
                <el-button
                  type="danger"
                  :icon="SwitchButton"
                  circle
                  plain
                  @click="handleKickout(scope.row)"
                  v-hasPerm="[DELETE + AUTH_PATH_WEB_PREFIX + '/account/kickout']"
                ></el-button>
                </div>
              </template>
            </el-table-column>
          </el-table>

          <pagination
            v-if="total > 0"
            :total="total"
            v-model:page="queryParams.current"
            v-model:limit="queryParams.pageSize"
            @pagination="handleQuery"
            v-hasPerm="[GET + AUTH_PATH_WEB_PREFIX + '/user/page']"
          />
        </el-card>
      </el-col>
    </el-row>

    <!-- 用户表单 -->
    <el-dialog
      :title="dialog.title"
      v-model="dialog.visible"
      width="600px"
      append-to-body
      @close="cancel"
    >
      <el-form
        ref="dataFormRef"
        :model="formData"
        :rules="rules"
        label-width="80px"
      >
        <el-form-item label="账号" prop="username" v-if="state.dialog.title=='添加用户'">
          <el-input v-model="formData.username" placeholder="请输入账号"/>
        </el-form-item>
        <el-form-item label="姓名" prop="realName">
          <el-input v-model="formData.realName" placeholder="请输入姓名"/>
        </el-form-item>
        <el-form-item label="昵称" prop="nickname">
          <el-input v-model="formData.nickname" placeholder="请输入用户昵称"/>
        </el-form-item>
        <!-- 下拉多选 -->
        <el-form-item label="部门" prop="deptId">
          <el-tree-select
            v-model="formData.deptId"
            placeholder="请选择所属部门"
            :data="deptOptions"
            filterable
            check-strictly
            multiple
          />
        </el-form-item>
        <!-- 下拉单选 -->
        <!--<el-form-item label="部门" prop="deptId">
          <el-tree-select
            v-model="formData.deptId"
            placeholder="请选择所属部门"
            :data="deptOptions"
            filterable
            check-strictly
          />
        </el-form-item>-->
        <el-form-item label="手机" prop="phone">
          <el-input
            v-model="formData.phone"
            placeholder="请输入手机号码"
            maxlength="11"
          />
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input
            v-model="formData.email"
            placeholder="请输入邮箱"
            maxlength="50"
          />
        </el-form-item>
        <el-form-item label="状态" prop="state">
          <el-radio-group v-model="formData.state">
            <el-radio :label="'enabled'">正常</el-radio>
            <el-radio :label="'stop'">禁用</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="性别" prop="gender">
          <el-select v-model="formData.gender" placeholder="请选择">
            <el-option label="未知" :value="''"/>
            <el-option label="男" :value="'man'"/>
            <el-option label="女" :value="'woman'"/>
          </el-select>
        </el-form-item>
        <!-- 下拉多选 -->
        <el-form-item label="角色" prop="roleId">
          <el-select v-model="formData.roleId" multiple placeholder="请选择">
            <el-option
              v-for="item in roleOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <!-- 下拉单选 -->
        <!--<el-form-item label="角色" prop="roleId">
          <el-tree-select
            v-model="formData.roleId"
            placeholder="请选择角色"
            :data="roleOptions"
            filterable
            check-strictly
          />
        </el-form-item>-->
      </el-form>

      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确 定</el-button>
          <el-button @click="cancel">取 消</el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog
      :title="importDialog.title"
      v-model="importDialog.visible"
      width="600px"
      append-to-body
      @close="closeImportDialog"
    >
      <el-form
        ref="importFormRef"
        :model="importFormData"
        :rules="rules"
        label-width="80px"
      >
        <el-form-item label="部门" prop="deptId">
          <el-tree-select
            v-model="formData.deptId"
            placeholder="请选择部门"
            :data="deptOptions"
            filterable
            check-strictly
          />
        </el-form-item>

        <el-form-item label="角色" prop="roleId">
          <el-select
            v-model="importFormData.roleId"
            multiple
            placeholder="请选择"
          >
            <el-option
              v-for="item in roleOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>

        <el-form-item label="Excel">
          <el-upload
            class="upload-demo"
            action=""
            drag
            :auto-upload="false"
            accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
            :file-list="excelFilelist"
            :on-change="handleExcelChange"
            :limit="1"
          >
            <el-icon class="el-icon--upload">
              <upload-filled/>
            </el-icon>
            <div class="el-upload__text">
              将文件拖到此处，或
              <em>点击上传</em>
            </div>
            <template #tip>
              <div class="el-upload__tip">xls/xlsx files</div>
            </template>
          </el-upload>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitImportForm">确 定</el-button>
          <el-button @click="closeImportDialog">取 消</el-button>
        </div>
      </template>
    </el-dialog>

  </div>
</template>

<script setup lang="ts">
  // Vue依赖
  import {
    reactive,
    ref,
    watchEffect,
    onMounted,
    getCurrentInstance,
    toRefs,
  } from 'vue';

  // 导入API
  import {
    listUsersPage,
    getUserFormData,
    deleteUsers,
    kickoutUsers,
    addUser,
    updateUser,
    updateUserPart,
    updateUserState,
    downloadTemplate,
    exportUser,
    importUser,
  } from '@/api/system/user';
  import {listSelectDepartments} from '@/api/system/dept';
  import {listSelectRoles} from '@/api/system/role';
  // @ts-ignore
  import {POST, DELETE, PUT, PATCH, GET} from '@/types/store/prefix.d.ts';

  // 组件依赖
  import {
    ElMessage,
    ElMessageBox,
    ElTree,
    ElForm,
    UploadFile,
  } from 'element-plus';
  import {
    Search,
    Plus,
    Edit,
    Refresh,
    Delete,
    Lock,
    Download,
    Top,
    UploadFilled,
    SwitchButton,
  } from '@element-plus/icons-vue';
  import {
    UserPage,
    UserQueryParam,
    UserFormData,
    UserImportFormData,
  } from '@/types/api/system/user';

  import {Option, Dialog} from '@/types/common';

  // DOM元素的引用声明定义 ，变量名和DOM的ref属性值一致
  const deptTreeRef = ref(ElTree); // 部门树
  const queryFormRef = ref(ElForm); // 查询表单
  const dataFormRef = ref(ElForm); // 用户表单
  const importFormRef = ref(ElForm); // 导入表单

  const {proxy}: any = getCurrentInstance();

  const state = reactive({
    // 遮罩层
    loading: true,
    // 选中数组
    ids: [],
    realNameList: [],
    // 非单个禁用
    single: true,
    // 非多个禁用
    multiple: true,
    // 总条数
    total: 0,
    // 用户分页数据
    userList: [] as UserPage[],
    // 弹窗属性
    dialog: {
      visible: false,
    } as Dialog,
    deptName: undefined,
    // 部门树选项
    deptOptions: [] as Option[],
    // 部门名称
    // 性别状态字典
    genderOptions: [] as any[],
    // 角色选项
    roleOptions: [] as Option[],
    // 表单参数
    formData: {
      state: 1,
    } as UserFormData,
    // 查询参数
    queryParams: {
      current: 1,
      pageSize: 10,
    } as UserQueryParam,
    // 表单校验
    rules: {
      username: [{required: true, message: '用户名不能为空', trigger: 'blur'}],
      nickname: [{required: true, message: '用户昵称不能为空', trigger: 'blur'},],
      deptId: [{required: true, message: '所属部门不能为空', trigger: 'blur'}],
      roleId: [{required: true, message: '用户角色不能为空', trigger: 'blur'}],
      email: [
        {
          pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
          message: '请输入正确的邮箱地址',
          trigger: 'blur',
        },
      ],
      phone: [
        {
          pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
          message: '请输入正确的手机号码',
          trigger: 'blur',
        },
      ],
    },

    importDialog: {
      title: '用户导出',
      visible: false,
    } as Dialog,
    importFormData: {} as UserImportFormData,
    excelFile: undefined as any,
    excelFilelist: [] as File[],
  });

  const {
    loading,
    multiple,
    queryParams,
    userList,
    total,
    dialog,
    formData,
    rules,
    deptName,
    deptOptions,
    roleOptions,
    importDialog,
    importFormData,
    excelFilelist,
  } = toRefs(state);

  /**
   * 部门筛选
   */
  watchEffect(
    () => {
      deptTreeRef.value.filter(state.deptName);
    },
    {
      flush: 'post', // watchEffect会在DOM挂载或者更新之前就会触发，此属性控制在DOM元素更新后运行
    }
  );

  /**
   * 部门列表筛选
   */
  function filterDeptNode(value: string, data: any) {
    if (!value) {
      return true;
    }
    return data.label.indexOf(value) !== -1;
  }

  /**
   * 部门树节点点击事件
   */
  function handleDeptNodeClick(data: { [key: string]: any }) {
    state.queryParams.deptId = data.value;
    handleQuery();
  }

  /**
   * 加载角色数据
   */
  async function loadRoleOptions() {
    listSelectRoles().then((response) => {
      state.roleOptions = response.data;
    });
  }

  /**
   * 用户状态修改
   */
  function handleStatusChange(row: any) {
    const text = row.state === 'enabled' ? '启用' : '停用';
    ElMessageBox.confirm(
      '确认要' + text + '用户[' + row.nickname + ']吗?',
      '警告',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }
    )
      .then(() => {
        return updateUserState(row.id, {state: row.state});
      })
      .then(() => {
        ElMessage.success(text + '成功');
      })
      .catch(() => {
        row.state = row.state === 'enabled' ? 'stop' : 'enabled';
      });
  }

  /**
   * 用户查询
   **/
  function handleQuery() {
    state.loading = true;
    listUsersPage(state.queryParams).then(({data}) => {
      state.userList = data.list;
      state.total = data.total;
      state.loading = false;
    });
  }

  /**
   * 重置查询
   */
  function resetQuery() {
    queryFormRef.value.resetFields();
    handleQuery();
  }

  /**
   * 表格行选中事件
   */
  function handleSelectionChange(selection: any) {
    state.ids = selection.map((item: any) => item.id);
    state.realNameList = selection.map((item: any) => item.realName);
    state.single = selection.length !== 1;
    state.multiple = !selection.length;
  }

  /**
   * 密码重置
   */
  function resetPassword(row: { [key: string]: any }) {
    ElMessageBox.prompt(
      '请输入用户「' + row.realName + '」的新密码',
      '重置密码',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
      }
    )
      .then(({value}) => {
        if (!value) {
          ElMessage.warning('请输入新密码');
          return false;
        }
        updateUserPart({userId: row.id, password: value}).then(() => {
          ElMessage.success('修改成功，新密码是：' + value);
        });
      })
      .catch(() => {
      });
  }

  /**
   * 添加用户
   **/
  async function handleAdd() {
    await loadDeptOptions();
    await loadRoleOptions();
    state.dialog = {
      title: '添加用户',
      visible: true,
    };
  }

  /**
   * 修改用户
   **/
  async function handleUpdate(row: { [key: string]: any }) {
    const userId = row.id || state.ids;
    await loadDeptOptions();
    await loadRoleOptions();
    state.dialog = {
      title: '修改用户',
      visible: true,
    };
    getUserFormData(userId).then(({data}) => {
      state.formData = data;
    });
  }

  /**
   * 表单提交
   */
  function submitForm() {
    dataFormRef.value.validate((valid: any) => {
      if (valid) {
        const userId = state.formData.id;
        if (userId) {
          updateUser(userId, state.formData).then(() => {
            ElMessage.success('修改用户成功');
            cancel();
            handleQuery();
          });
        } else {
          addUser(state.formData).then(() => {
            ElMessage.success('新增用户成功');
            cancel();
            handleQuery();
          });
        }
      }
    });
  }

  /**
   * 删除用户
   */
  function handleDelete(row: { [key: string]: any }) {
    const tips = row.realName || state.realNameList.join(',');
    const userIds = row.id || state.ids.join(',');
    ElMessageBox.confirm(
      '是否确认删除用户「' + tips + '」的数据项?',
      '警告',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }
    )
      .then(function () {
        deleteUsers(userIds).then(() => {
          ElMessage.success('删除成功');
          handleQuery();
        });
      })
      .catch(() => ElMessage.info('已取消删除'));
  }

  /**
   * 踢下线用户
   */
  function handleKickout(row: { [key: string]: any }) {
    const tips = row.realName || state.realNameList.join(',');
    const userIds = row.id || state.ids.join(',');
    ElMessageBox.confirm(
      '是否确认踢下线用户「' + tips + '」的数据项?',
      '警告',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }
    )
      .then(function () {
        kickoutUsers(userIds).then(() => {
          ElMessage.success('踢下线成功');
          handleQuery();
        });
      })
      .catch(() => ElMessage.info('已取消踢下线'));
  }

  /**
   * 取消
   */
  function cancel() {
    state.dialog.visible = false;
    state.formData.id = undefined;
    dataFormRef.value.resetFields();
  }

  /**
   * 加载部门
   */
  async function loadDeptOptions() {
    listSelectDepartments().then((response) => {
      state.deptOptions = response.data;
    });
  }

  /**
   * 加载性别字典
   */
  function loadGenderOptions() {
    proxy.$getDictItemsByTypeCode('gender').then((response: any) => {
      state.genderOptions = response?.data;
    });
  }

  /**
   * 下载导入模板
   */
  function handleDownloadTemplate() {
    downloadTemplate().then((response: any) => {
      const blob = new Blob([response.data], {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8',
      });
      const a = document.createElement('a');
      const href = window.URL.createObjectURL(blob); // 下载链接
      a.href = href;
      a.download = decodeURI(
        response.headers['content-disposition'].split(';')[1].split('=')[1]
      ); // 获取后台设置的文件名称
      document.body.appendChild(a);
      a.click(); // 点击下载
      document.body.removeChild(a); // 下载完成移除元素
      window.URL.revokeObjectURL(href); // 释放掉blob对象
    });
  }

  /**
   * 显示导入弹窗
   */
  async function showImportDialog() {
    await loadDeptOptions();
    await loadRoleOptions();
    state.importDialog.visible = true;
  }

  /**
   * Excel文件change事件
   *
   * @param file
   */
  function handleExcelChange(file: UploadFile) {
    if (!/\.(xlsx|xls|XLSX|XLS)$/.test(file.name)) {
      ElMessage.warning('上传Excel只能为xlsx、xls格式');
      state.excelFile = undefined;
      state.excelFilelist = [];
      return false;
    }
    state.excelFile = file.raw;
  }

  /**
   * Excel文件上传
   */
  function submitImportForm() {
    importFormRef.value.validate((valid: any) => {
      if (valid) {
        if (!state.excelFile) {
          ElMessage.warning('上传Excel文件不能为空');
          return false;
        }

        const deptId = state.importFormData.deptId;
        const roleId = state.importFormData.roleId;
        importUser(deptId, roleId, state.excelFile).then((response) => {
          ElMessage.success(response.data);
          closeImportDialog();
          handleQuery();
        });
      }
    });
  }

  /**
   * 关闭导入弹窗
   */
  function closeImportDialog() {
    state.importDialog.visible = false;
    state.excelFile = undefined;
    state.excelFilelist = [];
    importFormRef.value.resetFields();
  }

  /**
   * 导出用户
   */
  function handleExport() {
    exportUser(queryParams.value).then((response: any) => {
      const blob = new Blob([response.data], {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8',
      });
      const a = document.createElement('a');
      const href = window.URL.createObjectURL(blob); // 下载的链接
      a.href = href;
      a.download = decodeURI(
        response.headers['content-disposition'].split(';')[1].split('=')[1]
      ); // 获取后台设置的文件名称
      document.body.appendChild(a);
      a.click(); // 点击导出
      document.body.removeChild(a); // 下载完成移除元素
      window.URL.revokeObjectURL(href); // 释放掉blob对象
    });
  }

  /**
   * 初始化数据
   */
  function loadData() {
    // 初始化性别字典
    loadGenderOptions();
    // 初始化部门
    loadDeptOptions();
    // 初始化用户列表数据
    handleQuery();
  }

  onMounted(() => {
    loadData();
  });
</script>

<style lang="scss" scoped></style>
